package com.zghhost.codedemo.weight;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 
 * @department 校园招聘事业部
 * @author guohua.zhang guohua.zhang@zhaopin.com.cn
 * @date 2018-10-22 11:27:27
 * 分享抽奖活动
 * 目的：中奖位置按总预估次数均匀分布；降低同一个人多次中奖概率；如果总参与次数达到预估次数应尽量保证全部奖品命中；
 * 输入：奖品列表（含库存）；当前用户周期内已中奖次数；中奖概率（推荐用剩余奖品总量/剩余次数）；
 */
public class LotteryUtil {
	/**
	 * @param awards 奖品列表 <唯一标识,余库存>
	 * @return 命中奖品唯一标识 null 未命中
	 */
	public static <T> T doDraw(Map<T,Integer> awards) {
		return doDraw(awards,0);
	}
	
	/**
	 * @param awards 奖品列表 <唯一标识,余库存>
	 * @param winCount 当前中奖者已中奖次数
	 * @return 命中奖品唯一标识 null 未命中
	 * 默认中奖率 1/10
	 */
	public static <T> T doDraw(Map<T,Integer> awards,Integer winCount) {
		return doDraw(awards,winCount,0.1D);
	}
	
	/**
	 * @param awards 奖品列表 <唯一标识,余库存>
	 * @param winRate 中奖率
	 * @return 命中奖品唯一标识 null 未命中
	 */
	public static <T> T doDraw(Map<T,Integer> awards,Double winRate) {
		return doDraw(awards,0,winRate);
	}
	
	/**
	 * @param awards 奖品列表 <唯一标识,余库存>
	 * @param winCount 当前中奖者已中奖次数
	 * @param winRate 中奖率
	 * @return 命中奖品唯一标识 null 未命中
	 * 默认中奖率 1/10
	 */
	public static <T> T doDraw(Map<T,Integer> awards,Integer winCount,Double winRate) {
		if(winRate <= 0) {
			return null;// 中奖率负数
		}
		//排除库存0的奖品
		Map<T, Integer> awardWeightHaveStockMap = awards.entrySet().stream()
				.filter(e -> awards.get(e.getKey()) > 0)
				.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
		//奖品列表中奖品总数
		int totalWeight = (int) awardWeightHaveStockMap.values().stream().collect(Collectors.summarizingInt(i->i)).getSum();
		if(totalWeight <= 0) {
			return null;// 没有可用奖品
		}
		//增加未中奖到奖品列表中 权重根绝设置的中奖比例计算
		Double unhitWeight = Math.abs(totalWeight / winRate - totalWeight);
		if(winCount > 0) {
			//如果该用户已经中过奖 将未中奖概率放大n*50倍
			unhitWeight = unhitWeight * winCount * 50;
		}
		
		awardWeightHaveStockMap.put(null, unhitWeight.intValue());
		totalWeight+=unhitWeight.intValue();//将未中奖权重加入到总权
		int randNum = new Random().nextInt(totalWeight); //生成一个随机数
	    int prev = 0;
	    T choosedAward = null;
	    // 按照权重计算中奖区间
	    for(Entry<T,Integer> e : awardWeightHaveStockMap.entrySet()){
	        if(randNum>=prev && randNum<prev+e.getValue()){
	            choosedAward = e.getKey(); //落入该奖品区间
	            break;
	        }
	        prev = prev+e.getValue();
	    }
		return choosedAward;
	}
	
	
	
	
	public static void main(String[] args) throws InterruptedException {
		System.out.println("--------------single---------------");
		singleThreadTest();
		System.out.println("--------------single---------------");
		System.out.println("--------------multi---------------");
		new LotteryUtil().new MultiThreadTest().test();;
		System.out.println("--------------multi---------------");
	}
	
	public static void singleThreadTest(){
		//总余次
		Integer totalCount = 1000;
		Integer leftCount = totalCount;
		
		//奖品列表
		Map<String,Integer> awards = new ConcurrentHashMap<String,Integer>();
		awards.put("1", 50);
		awards.put("2", 20);
		awards.put("3", 3);
		
		List<Integer> hitRecord = new ArrayList<Integer>();
		
		while(leftCount-- >= 0) {
			//总奖品数量
			int leftPrize = (int) awards.values().stream().collect(Collectors.summarizingInt(i->i)).getSum();
			
			double winRate = leftPrize * 1.0 / leftCount;

			//抽奖
			String hit = doDraw(awards,0,winRate);
			
			if(hit != null) {
//				System.out.println("剩余"+leftCount+"次的时候中了"+hit);
				awards.put(hit, awards.get(hit)-1);
				hitRecord.add(totalCount-leftCount);
			}
			leftPrize = (int) awards.values().stream().collect(Collectors.summarizingInt(i->i)).getSum();
			if(leftPrize == 0) {
				System.out.println("奖品已经抽完");
				System.out.println("还有"+leftCount+"次机会没用");
				break;
			}
		}
		System.out.println("奖品库存："+awards);
		System.out.println("中奖位置"+hitRecord);
	}
	
	private class MultiThreadTest{
		//忽略线程安全
		
		//总余次
		Integer totalCount = 100000;
		Integer leftCount = totalCount;
		@SuppressWarnings("static-access")
		public void test() throws InterruptedException {
			System.out.println("线程模拟用户查看单用户多次中奖情况 忽略线程安全");
			
			//奖品列表
			Map<String,Integer> awards = new ConcurrentHashMap<String,Integer>();
			awards.put("1", 600);
			awards.put("2", 400);
			awards.put("3", 30);

			Map<String,Integer> hitThread = new ConcurrentHashMap<String,Integer>();
			
			List<Integer> hitRecord = new ArrayList<Integer>();
			
			//加入每个用户可以抽奖20次
			int threadCount = totalCount / 5;
			while(threadCount-- >= 0) {
				new Thread() {
					@Override
					public void run() {
						int tc = 5;
						String tid = ""+currentThread().getId();
						while(tc-- >=0 && leftCount-- >= 0) {
							//总奖品数量
							int leftPrize = (int) awards.values().stream().collect(Collectors.summarizingInt(i->i)).getSum();
							
							double winRate = leftPrize * 1.0 / leftCount;

							//已中奖次数
							int  winCount = 0;
							if(hitThread.containsKey(tid)) {
								winCount = hitThread.get(tid);
							}
							//抽奖
							String hit = doDraw(awards,winCount,winRate);
							
							if(hit != null) {
								awards.put(hit, awards.get(hit)-1);
								hitRecord.add(totalCount-leftCount);
								
								if(hitThread.containsKey(tid)) {
									hitThread.put(tid, hitThread.get(tid)+1);
								}else {
									hitThread.put(tid, 1);
								}
							}
							leftPrize = (int) awards.values().stream().collect(Collectors.summarizingInt(i->i)).getSum();
							if(leftPrize == 0) {
								
								break;
							}
						}
					}
				}.start();
			}
			new Thread().sleep(3000);
			System.out.println("奖品已经抽完");
			System.out.println("还有"+leftCount+"次机会没用");
			System.out.println("奖品库存："+awards);
			System.out.println("中奖位置"+hitRecord);
			System.out.println("每人中奖次数"+hitThread);
			int biggerOneCount = (int)hitThread.entrySet().stream().filter(e -> hitThread.get(e.getKey()) > 1).collect(Collectors.summarizingInt(i->1)).getSum();
			System.out.println("大于1次的中奖人有"+biggerOneCount);
		}
	}
}
